﻿/*
 * Copyright © 2010 Caduceuscoil. All rights reserved.
 * 
 * This file is part of Search Registry.
 * 
 * RegSearch is open-source software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free Software Foundation,
 * either version 2 of the License, or (at your option) any later version.
 * 
 * RegSearch is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with Velodoc XP Edition.
 * If not, see <http://www.gnu.org/licenses/>.
 *  
 */
/* CHANGE LOG
 * Last Modified: 10/22/2010
 * Modified By: CC.
 * -----------------------------------------
 * Initial Version: 10/11/2010
 * Created By: CC.
 * 
 * 
 * BUGS:
 * 
 * 
 * 
 * TODO:
 * 
 */

using System;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
//
namespace SearchRegistry
{
    /// <summary>
    /// 
    /// </summary>
    class Program
    {
        //Setup registry variables
        static RegistryKey _regHK_ROOT = null;
        //Impersonation Token for registry searches
        static IntPtr _userToken;
        //
        static AssemblyTitleAttribute _appTitle;
        static AssemblyDescriptionAttribute _appDescription;
        static Stopwatch _searchTimer;
        //Future
        //static BackgroundWorker _searchWorker;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //Setup Search variables
            StringComparison comparisonType;
            string searchTitle;
            string searchTerm = string.Empty;
            List<string> searchExclude = new List<string>();
            string remoteServer = string.Empty;
            string remoteUser = string.Empty;
            string remotePass = string.Empty;
            bool searchHKLM, searchHKCU, searchHKCR;
            bool ignoreCase;
            
            //For visual confirmation
            searchHKLM = true;
            searchHKCU = true;
            //For visual confirmation
            searchHKCR = false;
            ignoreCase = false;
            
            //Get application title and description
            _appTitle = (AssemblyTitleAttribute)AssemblyTitleAttribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyTitleAttribute));
            _appDescription = (AssemblyDescriptionAttribute)AssemblyTitleAttribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyDescriptionAttribute));
            
            //Setup the elapsed timer
            _searchTimer = new Stopwatch();

            //Handle the CTRL+C event
            Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
            
            //Setup Search Worker (Future)
            //_searchWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
            //_searchWorker.ProgressChanged += new ProgressChangedEventHandler(_searchWorker_ProgressChanged);


            //Check and parse incoming command arguments
            //There are many ways to do this parsing, I chose this way to do it.
            if (args.Length > 0) { 
                foreach (string param in args){
                    if (param.StartsWith("-"))
                    {
                        //Help
                        if (param.Substring(1).ToUpper().CompareTo("?").Equals(0)) { searchTerm = string.Empty; break; }
                        //Ignore Case
                        if (param.Substring(1).ToUpper().CompareTo("IGNORE").Equals(0)) { ignoreCase = true; }
                        //Registry Hive Keys to Search
                        if (param.Substring(1).ToUpper().CompareTo("HKLM").Equals(0)) { searchHKCU = false; }
                        if (param.Substring(1).ToUpper().CompareTo("HKCU").Equals(0)) { searchHKLM = false; }
                        if (param.Substring(1).ToUpper().CompareTo("HKCR").Equals(0)) { searchHKCR = true; searchHKCU = false; searchHKLM = false; }

                        //Setup the search exclusion
                        string[] temp = param.Substring(1).Split(':');
                        if (param.Substring(1, temp[0].Length).ToUpper().CompareTo("EXCLUDE").Equals(0))
                        {
                            if (temp.Length == 2)
                            {
                                string[] excludes = temp[1].Split(',');
                                foreach (string exclude in excludes)
                                    searchExclude.Add(exclude);
                            }
                        }
                        //Remote Server Name
                        if (param.Substring(1, temp[0].Length).ToUpper().CompareTo("REMOTE").Equals(0))
                        {
                            if (temp.Length == 2)
                                remoteServer = temp[1];
                        }
                        //Remote Server Username
                        if (param.Substring(1, temp[0].Length).ToUpper().CompareTo("USER").Equals(0))
                        {
                            if (temp.Length == 2)
                                remoteUser = temp[1];
                        }
                        //Remote Server Password 
                        if (param.Substring(1, temp[0].Length).ToUpper().CompareTo("PASS").Equals(0))
                        {
                            if (temp.Length == 2)
                                remotePass = temp[1];
                        }
                    }//Not a parameter, then it's the search string
                    else { searchTerm += string.Format("{0} ", param); }
                }
                //Trim and trailing/leading
                searchTerm = searchTerm.Trim();
            }
#if DEBUG
            Console.WriteLine(string.Format("Starting as user: {0} / {1}", WindowsIdentity.GetCurrent().Name, WindowsIdentity.GetCurrent().ImpersonationLevel));
#endif

            //Set string comparison
            comparisonType = ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
            //
            if (!string.IsNullOrEmpty(searchTerm))
            {
                try
                {
                    //Do the search as either the local user or impersonated user
                    //depending on the context of searchIdentity
                    if (!string.IsNullOrEmpty(remoteServer))
                        _userToken = SearchImpersonation.GetUserToken(remoteUser, remotePass);
                    else
                        _userToken = SearchImpersonation.GetUserToken();

                    if (!IntPtr.Zero.Equals(_userToken))
                    {
                        using (WindowsImpersonationContext searchContext = WindowsIdentity.Impersonate(_userToken))
                        {
#if DEBUG
                            Console.WriteLine(string.Format("Searching as user: {0} / {1}", WindowsIdentity.GetCurrent().Name, WindowsIdentity.GetCurrent().ImpersonationLevel));
#endif
                            //Start the search timer
                            _searchTimer.Start();
                            if (searchHKLM)
                            {
                                _regHK_ROOT = GetRootKey(remoteServer, RegistryHive.LocalMachine);
                                searchTitle = DisplaySearchKeyName(_regHK_ROOT, searchTerm, searchExclude, remoteServer);
                                //
                                ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Green);
                                Console.WriteLine(searchTitle);
                                Console.Title = searchTitle;
                                ConsoleColor.SetConsoleColor();
                                //
                                SearchSubKeys(_regHK_ROOT, searchTerm, searchExclude, comparisonType);
                                _regHK_ROOT.Close();
                            }

                            if (searchHKCU)
                            {
                                //
                                _regHK_ROOT = GetRootKey(remoteServer, RegistryHive.CurrentUser);
                                searchTitle = DisplaySearchKeyName(_regHK_ROOT, searchTerm, searchExclude, remoteServer);
                                //
                                ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Green);
                                Console.WriteLine(searchTitle);
                                Console.Title = searchTitle;
                                ConsoleColor.SetConsoleColor();
                                //
                                SearchSubKeys(_regHK_ROOT, searchTerm, searchExclude, comparisonType);
                                _regHK_ROOT.Close();
                            }

                            if (searchHKCR)
                            {
                                _regHK_ROOT = GetRootKey(remoteServer, RegistryHive.ClassesRoot);
                                searchTitle = DisplaySearchKeyName(_regHK_ROOT, searchTerm, searchExclude, remoteServer);
                                //
                                ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Magenta);
                                Console.WriteLine(searchTitle);
                                Console.Title = searchTitle;
                                ConsoleColor.SetConsoleColor();
                                //
                                SearchSubKeys(_regHK_ROOT, searchTerm, searchExclude, comparisonType);
                                _regHK_ROOT.Close();
                            }
                            //Resume control to calling user
                            searchContext.Undo();
                        }
                        //Close the token
                        SearchImpersonation.CloseToken(_userToken);
                    }
                }//
                catch (SecurityException e) { DisplayError(e); }
                catch (UnauthorizedAccessException e) { DisplayError(e); }
                catch (System.IO.IOException e) { DisplayError(e); }
                catch (System.ComponentModel.Win32Exception e) { DisplayError(e.Message); }
                //Show the elapsed time
                finally
                {
                    DisplayElapsedTimer(_searchTimer.Elapsed, false);
                    //Stop the timer
                    _searchTimer.Stop();
                }
            }
            else { ShowUsageHelp(_appTitle.Title, _appDescription.Description); }
            //
            CleanUp();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void _searchWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
          
        }
        /// <summary>
        /// Delegate Event to handle user pressing CTRL+? keys
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            //Show the elapsed time
            DisplayElapsedTimer(_searchTimer.Elapsed, false);
        }
        /// <summary>
        /// 
        /// </summary>
        static void CleanUp()
        {
            if (null != _regHK_ROOT)
                _regHK_ROOT.Close();

            //Close the token
            SearchImpersonation.CloseToken(_userToken);
        }

        #region Search Methods
        /// <summary>
        /// Recursive method to search registry keys
        /// </summary>
        /// <param name="key">RegistryKey object</param>
        /// <param name="search">Search term</param>
        /// <param name="exclude">Exclusion term</param>
        /// <param name="ignorecase">Flag to ignore case sensitive searches</param>
        static void SearchSubKeys(RegistryKey key, string search, List<string> exclude, StringComparison comparison)
        {
            if (string.IsNullOrEmpty(search)) { return; }
            //Regex to strip control characters from REG_BIN, no need to beep like hell
            Regex nonAscii = new Regex(@"[\x00-\x1f]");
            RegistryKey oSubKey;
            string subKeyValue = string.Empty;
            bool matchExclude = false;

            try
            {
                if (key.SubKeyCount > 0)
                {
                    foreach (string subkey in key.GetSubKeyNames())
                    {
                        try { oSubKey = key.OpenSubKey(subkey); }
                        //Display any access denied to registry keys/values and continue
                        catch (SecurityException e) { DisplayError(string.Format("{0} [{1}]", key.Name, e.Message)); continue; }

                        //Skip exclusions
                        if (exclude.Count > 0)
                        {
                            matchExclude = false;
                            //Check each exclusion for a match
                            exclude.ForEach(delegate(string item)
                            { if (subkey.IndexOf(item, comparison) >= 0) { matchExclude = true; } });
                            if (matchExclude) { continue; }
                        }

                        //Display search matches of top keys
                        if (subkey.IndexOf(search, comparison) >= 0)
                            Console.WriteLine(string.Format("{0}\\{1}", key.Name, subkey));
                        
                        //
                        foreach (string subvalue in oSubKey.GetValueNames())
                        {
                            //Skip exclusions
                            if (exclude.Count > 0)
                            {
                                matchExclude = false;
                                //Check each exclusion for a match
                                //A single match will increment the loop
                                exclude.ForEach(delegate(string item)
                                { if (subvalue.IndexOf(item, comparison) >= 0) { matchExclude = true; } });
                                if (matchExclude) { continue; }
                            }

                            //Display search matches of the sub keys
                            if (subvalue.IndexOf(search, comparison) >= 0)
                                Console.WriteLine(string.Format("{0}\\{1}", key.Name, subvalue));

                            try
                            {
                                RegistryValueKind valueKind = oSubKey.GetValueKind(subvalue);
                                //Parse Value Types and convert to string
                                switch (valueKind)
                                {
                                    //Convert common types to string
                                    case RegistryValueKind.String:
                                    case RegistryValueKind.MultiString:
                                    case RegistryValueKind.ExpandString:
                                    case RegistryValueKind.DWord:
                                        subKeyValue = Convert.ToString(oSubKey.GetValue(subvalue));
                                        break;
                                    //Convert the binary array to string
                                    case RegistryValueKind.Binary:
                                        System.Text.UTF8Encoding encoder = new UTF8Encoding();
                                        subKeyValue = encoder.GetString((byte[])oSubKey.GetValue(subvalue));
                                        subKeyValue = nonAscii.Replace(subKeyValue, string.Empty);
                                        break;
                                    //Ignore these registry types and continue
                                    case RegistryValueKind.QWord:
                                    case RegistryValueKind.Unknown:
                                    default:
                                        continue;
                                }
                                //Display search matches in the registry values
                                if (subKeyValue.IndexOf(search, comparison) >= 0)
                                    Console.WriteLine(string.Format("{0}\\{1}\\{2}:{3} = {4}", key.Name, subkey, subvalue, GetRegistryKind(valueKind), subKeyValue));

                            }//Display any access denied to registry keys/values and continue
                            catch (SecurityException e) { DisplayError(string.Format("{0}\\{1} [{2}]", key.Name, subkey, e.Message)); continue; }
                        }
                        //Recurse sub keys
                        SearchSubKeys(oSubKey, search, exclude, comparison);
                    }
                }
            }//Display any RegistryKey Class argument exceptions
            catch (ArgumentException e) { DisplayError(e); }
        }
        #endregion

        #region Display Console Messages Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="title"></param>
        /// <param name="description"></param>
        static void ShowUsageHelp(string title, string description)
        {
            //See Resources.resx files for globalization
            ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Yellow);
            Console.WriteLine(string.Format("{0}", Assembly.GetExecutingAssembly().FullName));
            Console.WriteLine(string.Format("{0}", description));
            Console.WriteLine();
            Console.WriteLine(string.Format(Resources.HELP_USE_1, title));
            Console.WriteLine(string.Format(Resources.HELP_USE_2, title));
            Console.WriteLine(string.Format(Resources.HELP_USE_3, title));
            Console.WriteLine(string.Format(Resources.HELP_USE_4, title));
            Console.WriteLine(string.Format(Resources.HELP_USE_5, title));
            Console.WriteLine(string.Format(Resources.HELP_USE_6, title));
            Console.WriteLine();
            Console.WriteLine(Resources.HELP_NOTE_1);
            Console.WriteLine(Resources.HELP_NOTE_2);
            Console.WriteLine(Resources.HELP_NOTE_3);
            Console.WriteLine(Resources.HELP_NOTE_4);
            Console.WriteLine(Resources.HELP_NOTE_5);
            Console.WriteLine();
            Console.WriteLine(string.Format(Resources.HELP_EXAMPLE_1, title));
            Console.WriteLine(Resources.HELP_EXAMPLE_1_Summary);
            Console.WriteLine();
            Console.WriteLine(string.Format(Resources.HELP_EXAMPLE_2, title));
            Console.WriteLine(Resources.HELP_EXAMPLE_2_Summary);
            Console.WriteLine();
            Console.WriteLine(string.Format(Resources.HELP_EXAMPLE_3, title));
            Console.WriteLine(Resources.HELP_EXAMPLE_3_Summary);
            ConsoleColor.SetConsoleColor();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="progress"></param>
        static void DisplayProgress(int progress, int total)
        {
            decimal percent = Math.Round((decimal)(total / progress));
            Console.Write(string.Format(Resources.DISPLAY_PROGRESS, percent));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="exp"></param>
        static void DisplayError(Exception exp)
        {
            ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Error);
            Console.WriteLine();
            Console.WriteLine(string.Format("{0}", exp));
            ConsoleColor.SetConsoleColor();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="exp"></param>
        static void DisplayError(string error)
        {
            ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Error);
            Console.WriteLine(string.Format("{0}", error));
            ConsoleColor.SetConsoleColor();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="timer"></param>
        static void DisplayElapsedTimer(TimeSpan timer,bool showInTitle)
        {
            if (showInTitle)
            {
                Console.Title = string.Format(Resources.DISPLAY_TITLE_TIMER, timer.Seconds);
            }
            else
            {
                ConsoleColor.SetConsoleColor(ConsoleColor.Foreground.Yellow);
                Console.WriteLine();
                Console.WriteLine(string.Format(Resources.DISPLAY_ELAPSED_TIMER, timer.Seconds));
                ConsoleColor.SetConsoleColor();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        static string DisplaySearchKeyName(RegistryKey key, string search, List<string> exclude, string host)
        {
            string temp = string.Empty;
            temp = string.Format(Resources.DISPLAY_SEARCH_KEY_MESSAGE, 
                (host.Length > 0) ? string.Format("\\\\{0}\\{1}",host, key.Name): key.Name,
                search,
                (exclude.Count > 0) ? string.Format(Resources.DISPLAY_SEARCH_KEY_EXCLUDE_MESSAGE, string.Join(",",exclude.ToArray())) : string.Empty).Trim();
            
            return temp;
        }
        #endregion

        #region Registry Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="server"></param>
        /// <returns></returns>
        static Microsoft.Win32.RegistryKey GetRootKey(string server, Microsoft.Win32.RegistryHive rootkey)
        {
            if (string.IsNullOrEmpty(server))
                switch (rootkey)
                {
                    case RegistryHive.ClassesRoot:
                        return Microsoft.Win32.Registry.ClassesRoot;
                    case RegistryHive.CurrentUser:
                        return Microsoft.Win32.Registry.CurrentUser;
                    case RegistryHive.CurrentConfig:
                        return Microsoft.Win32.Registry.CurrentConfig;
                    case RegistryHive.LocalMachine:
                    default:
                        return Microsoft.Win32.Registry.LocalMachine;
                }
            else
                return Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(rootkey, server);
                
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        static string GetRegistryKind(RegistryValueKind value)
        {
            switch (value)
            {
                case RegistryValueKind.Binary:
                    return "REG_BIN";
                case RegistryValueKind.DWord:
                    return "REG_DWORD";
                case RegistryValueKind.ExpandString:
                    return "REG_EXPAND_SZ";
                case RegistryValueKind.MultiString:
                    return "REG_MULTI_SZ";
                case RegistryValueKind.QWord:
                    return "REG_QWORD";
                case RegistryValueKind.String:
                    return "REG_SZ";
                case RegistryValueKind.Unknown:
                default:
                    return "REG_UNKOWN";
            }
        }
        #endregion
    }
}